...loading
2024-11-30
그동안 랜딩페이지의 디자인을 구상하고 구현하느라 시간이 좀 걸렸습니다. 깔끔하고 멋지게 디자인하고 싶지만, 디자인은 참 어려운거 같네요.. 아래와 같이 기본적인 틀은 잡아놨습니다.
이제 본격적인 기능 개발을 위해 로그인부터 시작해봅니다. 기능 구현은 next-auth를 사용해보고자 합니다. 버전은 최신 버전인 v4를 사용했습니다. 직접 인증에 관련한 서버 컴포넌트들을 구성해 인증처리를 할 수 있지만, nextJS에 최적화된 패키지이기에 직접 구현하는 방법 보다 안전하며 새로운 라이브러리를 써볼 겸 사용해봅니다.
패키지를 설치해봅니다. next-auth와 비밀번호 암호화를 위해 bcryptjs 패키지를 설치해줍니다. 그리고 이 프로젝트는 타입스크립트 프로젝트이기에 bcryptjs의 경우 타입패키지 또한 함께 받습니다.
npm install bcryptjs @type/bcryptjs npm install next-auth
이번 프로젝트는 개인 블로그이기 때문에 관리자의 아이디와 비밀번호만 존재하면 됩니다. DB에 직접 user collection을 추가하고 아이디와 수동으로 암호화한 비밀번호를 추가해줍니다. 이제부터 해당 user 정보를 통해 로그인 인증 기능을 구현할 수 있습니다.
이제 next-auth를 사용해봅니다. next-auth의 인증 기능을 사용하기 위해서는 API 라우트를 설정해야합니다.
pages/api/auth/[...nextauth].ts
위와 같은 라우팅을 설정합니다. 그리고 [...nextauth].ts 파일 내부에서 인증 로직을 구현합니다.
인증 구현을 위한 기본적인 틀은 아래와 같습니다.
// pages/api/auth/[...nextauth].ts import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export default NextAuth({ providers: [ CredentialsProvider({ name: "Credentials", credentials: { id: { label: "id", type: "text" }, password: { label: "password", type: "password" }, }, async authorize(credentials, req) { }, }), ], });
우선 인증을 위해 NextAuth 함수를 불러옵니다. 해당 함수는 Provider 설정을 통해 어떠한 인증을 사용할 것인지 설정하고, 구체적인 인증 로직을 담습니다.
제 프로젝트에서는 DB로부터 유저 정보를 직접 불러와 인증을 하기 때문에, Provider 객체에 CredentailsProvider만 추가해주면 됩니다. ( 혹시 다른 Provider를 설정하고 싶으면 해당링크에서 확인하면 됩니다.) 그리고 아래와 같이 credentials 필드에 필요한 객체의 구성요소를 적어줍니다.
credentials: { id: { label: "id", type: "text" }, password: { label: "password", type: "password" }, },
다음은 인증 로직을 구현할 차례입니다. 인증 로직은 async authorize 내부에 작성하면 됩니다. 아래는 인증 로직을 추가한 전체코드입니다. 이전에 작성해둔 DB 연결 로직을 통해 DB 클라이언트 객체를 생성하고, 유저정보를 불러와 아이디와 비밀번호를 확인합니다. 인증이 성공적으로 완료된다면, JWT가 발급되어 클라이언트의 쿠키에 저장됩니다. (JWT 설정은 디폴트값이기에 직접 설정할 필요는 없습니다.)
// pages/api/auth/[...nextauth].ts import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { connectDatabase } from "@/utils/db"; import { verifyPassword } from "@/utils/auth"; export default NextAuth({ providers: [ CredentialsProvider({ name: "Credentials", credentials: { id: { label: "id", type: "text" }, password: { label: "password", type: "password" }, }, async authorize(credentials, req): Promise<any> { // 로그인 인증로직 const client = await connectDatabase(); // DB객체 생성 const user = await client .db(process.env.DB_NAME) .collection("users") .findOne({ id: credentials?.id }); // CredentialsProvider 로부터 전달받은 아이디로 검색 if (!user) { throw new Error("Invalid user information!"); } const isValid = await verifyPassword( credentials?.password as string, user.password ); // CredentialsProvider 로부터 전달받은 비밀번호와 DB로부터 불러온 정보를 비교 if (!isValid) { throw new Error("Wrong password!"); } client.close(); // DB 객체 종료 return { user: user.id }; // 인증된 유저 정보를 리턴 (쿠키에 JWT 저장) }, }), ], });
이제 위의 로직을 로그인페이지와 연결하면 됩니다.next-auth에서 재공하는 signIn이라는 메서드를 통해 연결하면 됩니다. 그리고 해당 함수를 통해 직접 HTTP메서드를 작성할 필요없이, 앞선 인증 로직과 로그인 페이지를 연동할 수 있습니다. 아래는 로그인 페이지의 코드입니다.
root/app/login.tsx "use client"; import { FormEvent, useState } from "react"; import { signIn } from "next-auth/react"; export default function AuthForm() { function onSubmit(e: FormEvent) { e.preventDefault(); const result = signIn("credentials", { redirect: false, id: id, password: pw, }); } return ( <div className={styles["login-wrapper"]}> ... <button type="submit" className={styles["button"]} onClick={onSubmit}> login </button> ... </div> ); }
signIn 메서드는 client-side에 지원되는 메서드로 인증정보를 인증로직에 넘겨주는 기능이라 보면 될 것 같습니다. 첫번째 인자로 Provider를 명시해줍니다. 저의 프로젝트는 credenials로 명시해주면 됩니다. credentails 프로바이더를 사용할 경우 2번 째 인자로, 로그인 폼에서 작성한 유저 정보와 리다이렉트 여부를 써줍니다. redirect를 no로 설정할 경우, 로그인 실패 시 에러페이지로 이동하는 것이 아닌 로그인 페이지에 머무르도록 합니다.
이제 사용자 정보를 입력후, signIn 함수를 통해 이를 제출하면, [...nextauth].ts의 NextAuth함수에 유저정보가 전달되어 인증로직이 시행됩니다.
이제, 관리자 아이디와 비밀번호를 올바르게 입력하면 쿠키에 토큰이 발급되는 것을 확인할 수 있습니다. 이제 이 토큰을 사용해 관리자 기능에 대한 인가를 받을 준비가 되었습니다!
Comments